﻿using System;
using System.Configuration;
using System.Diagnostics;
using TfsCloudDeploy.Deployment.Framework;
using TfsCloudDeploy.Deployment.Framework.Interfaces;
using TfsCloudDeploy.Deployment.Service.DataContracts.Interfaces;
using TfsCloudDeploy.Deployment.Service.DataContracts;
using TfsCloudDeploy.Deployment.Framework.Helpers;
using TfsCloudDeploy.Deployment.Domain;

namespace TfsCloudDeploy.Deployment.Service.ServiceImplementation
{
    public abstract class BaseDeploymentService
    {
        #region Logging

        private static readonly ILog _log = new Log(typeof(BaseDeploymentService));

        #endregion

        private const int FAILURE_EXIT_CODE = 1;

        public virtual void DeployBuild(string buildPath)
        {
            DeployBuildEx(buildPath, null, null, false, false, false, false, false);

            // If the deployment script succeeded, check the logs to verify that the individual hosts got the code successfully:
            //VerifyDeployment();
        }

        public virtual void RollbackBuild(string buildPath)
        {
            DeployBuildEx(buildPath, null, null, true, false, false, false, false);
        }

        public virtual void TestBuild(string buildPath)
        {
            DeployBuildEx(buildPath, null, null, false, true, false, false, false);
        }

        public virtual void BringOnline(string buildPath)
        {
            DeployBuildEx(buildPath, null, null, false, false, true, false, false);
        }

        public virtual void TakeOffline(string buildPath)
        {
            DeployBuildEx(buildPath, null, null, false, false, false, true, false);
        }

        public virtual void BackupOnly(string buildPath)
        {
            DeployBuildEx(buildPath, null, null, false, false, false, false, true);
        }

        public virtual void DeployBuildEx(string buildPath, string username, string password, bool rollback, bool testMode, bool bringOnline, bool takeOffline, bool backup)
        {
            _log.Info("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
            _log.Info("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
            _log.Info("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
            _log.Info("DeploymentService::DeployBuildEx - ENTER");

            try
            {
                // Verify we have received a value for the build path
                if (string.IsNullOrWhiteSpace(buildPath))
                {
                    throw new ArgumentException("Input error: No value has been provided for required argument 'buildPath'.", "buildPath");
                }
                _log.InfoFormat("DeploymentService::ExecuteCommand - buildPath: {0}", buildPath);

                // Read the config settings
                string buildShareRootLocalPath = ConfigurationManager.AppSettings["BuildShareRootLocalPath"];
                string buildShareRootUncPath = ConfigurationManager.AppSettings["BuildShareRootUNCPath"];

                if (string.IsNullOrWhiteSpace(buildShareRootLocalPath) || string.IsNullOrWhiteSpace(buildShareRootUncPath))
                {
                    throw new Exception("Configuration error: One or both settings are not initialized: BuildShareRootLocalPath, BuildShareRootUNCPath. " +
                        "Verify that the settings are specified in web.config.");
                }

                _log.InfoFormat("DeploymentService::ExecuteCommand - buildShareRootLocalPath: {0}", buildShareRootLocalPath);
                _log.InfoFormat("DeploymentService::ExecuteCommand - buildShareRootUncPath: {0}", buildShareRootUncPath);

                // Construct the local path for the build
                string transformedPath = TransformBuildPathToLocalPath(buildPath, buildShareRootUncPath, buildShareRootLocalPath);
                string toolName = ConfigurationManager.AppSettings["ToolName"];
                string commandName = ConfigurationManager.AppSettings["DeployEntryFunction"];

                if (rollback)
                {
                    commandName = ConfigurationManager.AppSettings["RollbackEntryFunction"];
                }
                if (takeOffline)
                {
                    commandName = ConfigurationManager.AppSettings["TakeOfflineEntryFunction"];
                }
                if (bringOnline)
                {
                    commandName = ConfigurationManager.AppSettings["BringOnlineEntryFunction"];
                }
                if (backup)
                {
                    commandName = ConfigurationManager.AppSettings["BackupEntryFunction"];
                }
                if (testMode)
                {
                    commandName = ConfigurationManager.AppSettings["TestModeEntryFunction"];
                }

                string arguments = string.Format("{0} {1}", ConfigurationManager.AppSettings["ScriptCallArguments"], commandName);

                if (string.IsNullOrWhiteSpace(toolName))
                {
                    throw new Exception("Configuration error: ToolName is empty. Verify that the setting is specified in web.config.");
                }

                if (string.IsNullOrWhiteSpace(arguments))
                {
                    throw new Exception("Configuration error: ScriptCallArguments is empty. Verify that the setting is specified in web.config.");
                }

                ExecuteCommand(toolName, transformedPath, arguments);

                // Check if the deployment to one or more of the hosts has failed.
                DeploymentStatus status = DeploymentsHelper.GetDeploymentStatusForDeploymentBuild(transformedPath);
                if (status != DeploymentStatus.Succeeded)
                {
                    throw new Exception("ERROR: Deployment failed to one or more of the target hosts. Review the details on the 'Deployment Website', fix any issues, and then trigger a new deployment.");
                }
            }
            catch (Exception ex)
            {
                _log.InfoFormat("DeploymentService::DeployBuildEx - ERROR: {0}", ex.Message);
                _log.Error("DeploymentService::DeployBuildEx", ex);
                throw;
            }
            finally
            {
                _log.Info("DeploymentService::DeployBuildEx - EXIT");
                _log.Info("------------------------------------------------------------------------------------------------------------------------");
                _log.Info("------------------------------------------------------------------------------------------------------------------------");
                _log.Info("------------------------------------------------------------------------------------------------------------------------");
            }
        }

        protected string ExecuteCommand(string toolPath, string workingDirectory, string arguments)
        {
            _log.Info("DeploymentService::ExecuteCommand - ENTER");

            _log.InfoFormat("DeploymentService::ExecuteCommand - toolPath: {0}.", toolPath);
            _log.InfoFormat("DeploymentService::ExecuteCommand - workingDirectory: {0}.", workingDirectory);
            _log.InfoFormat("DeploymentService::ExecuteCommand - arguments: {0}.", arguments);

            int maximumProcessExitWaitInMilliseconds = Convert.ToInt32(ConfigurationManager.AppSettings["MaximumProcessExitWaitInMilliseconds"]);

            int exitCode = 0;

            ProcessStartInfo processInfo = new ProcessStartInfo();
            processInfo.CreateNoWindow = true;
            processInfo.UseShellExecute = false;
            processInfo.WindowStyle = ProcessWindowStyle.Hidden;
            processInfo.WorkingDirectory = workingDirectory;
            processInfo.RedirectStandardError = true;
            processInfo.RedirectStandardOutput = true;
            processInfo.FileName = toolPath;
            processInfo.Arguments = arguments;
            using (Process process = new Process())
            {
                process.StartInfo = processInfo;

                ProcessOutput processOutput = new ProcessOutput();

                process.OutputDataReceived += processOutput.OutputDataReceived;
                process.ErrorDataReceived += processOutput.ErrorDataReceived;

                bool result = process.Start();

                process.BeginOutputReadLine();
                process.BeginErrorReadLine();

                process.WaitForExit(maximumProcessExitWaitInMilliseconds);

                // Read what is already there in the standard output and standard error streams.
                string outputString = processOutput.StandardOutput;
                string errorString = processOutput.StandardError;
                //Trace.WriteLine(outputString);
                //Trace.WriteLine(errorString);
                _log.Info(outputString);
                _log.Info(errorString);

                if (process.HasExited)
                    exitCode = process.ExitCode;
                else
                {
                    process.Kill();
                    exitCode = FAILURE_EXIT_CODE;
                }

                if (!result || exitCode != 0)
                {
                    exitCode = FAILURE_EXIT_CODE;

                    return null;
                }

                return outputString;
            }
        }

        protected string TransformBuildPathToLocalPath(string originalPath, string baseUncPath, string baseLocalPath)
        {
            _log.InfoFormat("DeploymentService::TransformBuildPathToLocalPath - originalPath: ", originalPath);
            _log.InfoFormat("DeploymentService::TransformBuildPathToLocalPath - baseUncPath: ", baseUncPath);
            _log.InfoFormat("DeploymentService::TransformBuildPathToLocalPath - baseLocalPath: ", baseLocalPath);

            bool isUncPath = originalPath.StartsWith(baseUncPath, StringComparison.InvariantCultureIgnoreCase);
            bool isLocalPath = originalPath.StartsWith(baseLocalPath, StringComparison.InvariantCultureIgnoreCase);
            // validate that the root path is part of the provided original path
            if (!isUncPath && !isLocalPath)
            {
                throw new Exception(string.Format("Provided path '{0}' is invalid. It must start with '{1}' or '{2}'.", originalPath, baseUncPath, baseLocalPath));
            }

            if (isUncPath)
            {
                return originalPath.ToLowerInvariant().Replace(baseUncPath.ToLowerInvariant(), baseLocalPath.ToLowerInvariant());
            }
            else
            {
                return originalPath;
            }
        }

        protected void VerifyDeployment()
        {
            throw new Exception("Verification failed.");
        }
    }
}
